package br.ufc.custom.multiHistogram;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import br.ufc.custom.Graph.Graph;
import br.ufc.custom.Graph.MetisManager;
import br.ufc.custom.Graph.Vertex;
import br.ufc.parser.CustomInsert;
import br.ufc.parser.CustomStatement;
import br.ufc.parser.InsertColumn;
import br.ufc.parser.Predicate;
import br.ufc.parser.Query;
import br.ufc.parser.Value;
import br.ufc.util.NDimensionalArray;
import br.ufc.util.QueriesLoad;

public class MultiHistogram {

	private NDimensionalArray narray;
	private int dimensionsAmount;
	private int[] dimensionsSize;
	private List<String> dimensions;
	private Map<String, Integer> dimensionID;
	private List<Cell> cells;
	private Map<Integer, List<Value>> cuts;
	private QueriesLoad queriesLoad;


	public List<Cell> getCells() {
		return cells;
	}

	public MultiHistogram(QueriesLoad queriesLoad, int partitionsNumber, int shardsNumber) {
		this.queriesLoad = queriesLoad;
		this.dimensions = queriesLoad.getDimensions();
		this.dimensionsAmount = queriesLoad.getDimensions().size();
		this.dimensionsSize = new int[dimensionsAmount];
		this.dimensionID = new HashMap<String, Integer>();	
		this.cells = new ArrayList<Cell>();
		this.cuts = new HashMap<Integer, List<Value>>();


		for(int i = 0; i < this.dimensionsSize.length; i++){
			this.dimensionsSize[i] = shardsNumber / this.dimensionsSize.length; 
			if(i < shardsNumber % this.dimensionsSize.length){
				this.dimensionsSize[i] ++;				
			}	
			dimensionID.put(dimensions.get(i), i);
//			List<Value> cut_values = queriesLoad.getCuts(dimensions.get(i), this.dimensionsSize[i]);
			List<Value> cut_values = Arrays.asList(new Value(0.0), 
					new Value(660000.0), 
					new Value(1320000.0), 
					new Value(199999.0));
			cuts.put(i, cut_values);
			System.out.println(dimensions.get(i) + " "  + this.dimensionsSize[i]);
//			for(Value v: queriesLoad.getCuts(dimensions.get(i), this.dimensionsSize[i])){
//				System.out.print(v + " ");
//			}
			System.out.println("");
		}		

		this.narray = new NDimensionalArray(dimensionsSize);
		int iterator[] = new int[dimensionsAmount];
		for(int i = 0; i < dimensionsAmount; i++){
			iterator[i] = 0;
		}		

		boolean iterate = true;
		Cell cell = null;
		int cellID = 0;
		while(iterate){
			//action
			cell = new Cell(cellID);
			cellID ++;
			narray.set(iterator, cell);	
			cells.add(cell);
			for(String dim : dimensions){
				int dID = dimensionID.get(dim);
				System.out.println("dim: " + dim + " " + iterator[dID]);
				if(iterator[dID] != dimensionsSize[dID] - 1){
					cell.addPredicate(dim, "<=", cuts.get(dID).get(iterator[dID]+1));
					System.out.println(dim + " <= " + cuts.get(dID).get(iterator[dID]+1));
				}
				if(iterator[dID] != 0){
					cell.addPredicate(dim, ">", cuts.get(dID).get(iterator[dID]));
					System.out.println(dim + " > " + cuts.get(dID).get(iterator[dID]));
				}
			}
			for(int i = 0; i < dimensionsAmount; i ++){
				iterator[i] ++;			
				if(iterator[i] == dimensionsSize[i]){
					if(i == dimensionsAmount - 1){
						iterate = false;
						break;
					}
					iterator[i] = 0;					
				}
				else{
					break;
				}
			}
		}
		System.out.println("Construção do histograma");
		this.build();
		System.out.println("Particionamento");
//		this.partition(partitionsNumber);
		this.print();
	}

	private void partition(int partitionsNumber) {
		Graph g = new Graph(this);
		List<Vertex> vertices = g.getVertices();
		MetisManager mm = new MetisManager(g);
		List<Integer> parts = mm.execute(partitionsNumber, 1);
		for(int i = 0; i < parts.size(); i ++){
			vertices.get(i).setPartition(parts.get(i));
		}
	}

	public int computeCell(double value, double side, double min, double max){
		if(value < min ){
			value = min;
		}else if(value > max){
			value = max;
		}
		Double floor = Math.floor((value - min)/side);
		int cell = floor.intValue();
		if(value >= max && cell > 0){
			cell = cell - 1;
		}
		return cell;
	}

	private void build(){		

		for(CustomStatement q : this.queriesLoad.getStatements()){
			if(q.isSelect()){
				Query select = q.getSelect(); 
				Set<Predicate> predicates = select.getPredicates();
				List<Integer> cellsID = new ArrayList<Integer>(getCellsForSelect(select));
				for(int cellID : cellsID){
					this.cells.get(cellID).incrementCount();
				}
				for(int i = 0; i < cellsID.size(); i++){
					for(int j = i + 1; j < cellsID.size(); j++){
						Cell cell1 = this.cells.get(cellsID.get(i));
						Cell cell2 = this.cells.get(cellsID.get(j));
						cell1.incrementEdge(cell2);
						cell2.incrementEdge(cell1);
					}
				}
			}
		}
	}

	public Collection<Integer> getPartitionsForStatement(String sql){
		CustomStatement cs = new CustomStatement(sql);
		if(cs.isSelect()){
			return getPartitionsForSelect(cs.getSelect());
		}else if(cs.isInsert()){
			int cellID = getCellsForInsert(cs.getInsert());
			Cell cell = cells.get(cellID);
			return Arrays.asList(cell.getPartition());
		}
		return null;
	}

	public Collection<Integer> getPartitionsForSelect(Query q){
		Collection<Integer> partitions = new HashSet<Integer>();
		Collection<Integer> cellsID = getCellsForSelect(q);
		for(Integer cellID : cellsID){
			Cell cell = cells.get(cellID);
			partitions.add(cell.getPartition());
		}
		return partitions;		
	}

	public Collection<Integer> getCellsForStatement(String sql){
		return getCellsForStatement(new CustomStatement(sql));
	}

	public Collection<Integer> getCellsForStatement(CustomStatement statement){
		if(statement.isSelect()){
			return getCellsForSelect(statement.getSelect());
		}else if(statement.isInsert()){
			Collection<Integer> set = new HashSet<Integer>();
			set.add(getCellsForInsert(statement.getInsert()));
			return set;
		}
		return null;
	}

	public Collection<Integer> getCellsForSelect(Query q){
		Collection<Integer> cells = new HashSet<Integer>();
		Set<Predicate> predicates = q.getPredicates();

		boolean iterate = true;
		int iterator[] = new int[dimensionsAmount];
		for(int i = 0; i < dimensionsAmount; i++){
			iterator[i] = 0;
		}					

		while(iterate){
			boolean satisfy = true;
			Cell cell = (Cell)narray.get(iterator);
			for(Predicate predicate : predicates){	
				
				List<Predicate> compare = cell.getPredicate(predicate.getAttribute());
				if(compare == null){
					continue;
				}
				for(Predicate comparePredicate : compare){
					if(comparePredicate.getOperation().equals("<=") &&
							predicate.getOperation().equals(">") &&
							comparePredicate.getValue().compareTo(predicate.getValue()) <= 0){
						satisfy = false;
						break;
					}else if(comparePredicate.getOperation().equals(">") &&
							predicate.getOperation().equals("<") &&
							comparePredicate.getValue().compareTo(predicate.getValue()) > 0){
						satisfy = false;
						break;
					}else if(comparePredicate.getOperation().equals(">") &&
							predicate.getOperation().equals("=") &&
							comparePredicate.getValue().compareTo(predicate.getValue()) > 0){
						satisfy = false;
						break;
					}else if(comparePredicate.getOperation().equals("<=") &&
							predicate.getOperation().equals("=") &&
							comparePredicate.getValue().compareTo(predicate.getValue()) <= 0){
						satisfy = false;
						break;
					}					
				}
				if(!satisfy){
					break;
				}
			}
			if(satisfy){
				cells.add(cell.getID());
			}

			for(int i = 0; i < dimensionsAmount; i ++){
				iterator[i] ++;			
				if(iterator[i] == dimensionsSize[i]){
					if(i == dimensionsAmount - 1){
						iterate = false;
						break;
					}
					iterator[i] = 0;					
				}
				else{
					break;
				}
			}
		}
		return cells;
	}

	public int getCellsForInsert(CustomInsert q){
		List<InsertColumn> columns = q.getColumns();
		String sql = "SELECT * FROM \"usertable\" where";
		for(InsertColumn ic : columns){
			sql += " " + ic.getColumnName() + " = \'" + ic.getColumnValue().toString() + "\' AND";
		}
		sql = sql.substring(0, sql.length() - 4);
		System.out.println(sql);
		Query select = new Query(sql);
		Collection<Integer> cells = getCellsForSelect(select);
		if(cells.size() != 1){
			throw new RuntimeException("Should not happen");
		}
		return cells.iterator().next();
	}


	public NDimensionalArray getNarray() {
		return narray;
	}
	public int[] getDimensionsSize() {
		return dimensionsSize;
	}
	public int getDimensionsAmount() {
		return dimensionsAmount;
	}

	public void print(){
		int iterator[] = new int[dimensionsAmount];
		for(int i = 0; i < dimensionsAmount; i++){
			iterator[i] = 0;
		}		

		boolean iterate = true;
		System.out.print("DELETE FROM \"USERTABLE\" WHERE ");
		while(iterate){
			Cell cell = (Cell)narray.get(iterator);
			Integer value = cell.getCount();
			for(int i : iterator){
//				System.out.print(i + " ");
			}
//			System.out.println(" Value -> " + value + " Partition -> " + cell.getPartition() + " " + cell.createPredicates() );
			if(cell.getPartition() != 2){
				System.out.print("( " + cell.createPredicates() + " ) OR " );
			}

			for(int i = 0; i < dimensionsAmount; i ++){
				iterator[i] ++;			
				if(iterator[i] == dimensionsSize[i]){
					if(i == dimensionsAmount - 1){
						iterate = false;
						break;
					}
					iterator[i] = 0;					
				}
				else{
					break;
				}
			}
		}
	}
}